java快速学习速查(7)[进阶篇]
java快速学习速查(7)[进阶篇]
JBDC
JDBC:java语言连接数据库,通过java语言操作数据库中的数据。JDBC(Java Database Connectivity)是sun公司指定的一套标准规范,由很多的类和接口组成,在java.sql.*包下。
JDBC本质上是sun公司提供的一套接口,接口的实现类由数据库厂商提供。
- sum公司定义的一套操作所有关系型数据库的规范,即接口。
- 各个数据库厂商去实现这套接口,提供数据库驱动jar包。
- 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
这玩意的存在,为世界上所有的关系型数据库提供了统一的访问方式。是编程中访问数据库的标准规范。
各数据库厂商使用相同的接口,java代码不需要针对不同的数据库分别开发。
可以随时替换叠层数据库,访问数据库的java代码基本不变,以后编写操作数据库的代码只需要面向JDBC(接口),操作哪个关系型数据库就需要导入该数据库的驱动jar包,需要操作mysql数据库,就需要在项目中导入mysql数据库的驱动包。
java访问数据库(流程和对应的方法)
在java中访问数据库有几个步骤:
1.加载并注册数据库驱动
2.建立数据连接(URL书写,用户名,密码)
3.创建Statement
4.执行对数据库的增删改查操作
5.结果集处理
对应以上步骤的方法名是:
- 加载并注册数据库驱动:Class.forName(“com.mysql.jdbc.Driver”);
- 建立数据连接:Connection conn = DriverManager.getConnection(“jdbc:mysql://localhost:3306/db_name”, “username”, “password”);
- 创建Statement:Statement stmt = conn.createStatement();
- 执行对数据库的增删改查操作:int rowsAffected = stmt.executeUpdate(“INSERT INTO users (name, age) VALUES (‘John Doe’, 30)”);
- 结果集处理:ResultSet rs = stmt.executeQuery(“SELECT * FROM users”);
- 关闭连接:rs.close(); stmt.close(); conn.close();
我们在进行数据库和java连接前,请一定注意将对应版本的驱动包导入项目中。
(mysql-connector-java-5.1.37-bin.jar)添加到工程中的classpath里面(右键add as library),我们可以直接拷贝jar包。
连接方式上,我们可以直接在对应的行为类中进行连接,也可以在工具类中进行连接。
直接连接1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.iweb.test
import java.sql.Connection;
import java.sql.DriverManager;
public class Test1 {
public static void main(String[] args) throws Exception {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 驱动管理类获得连接对象
// url:连接的数据库的地址
// user: 连接数据库的账号
// pass: 连接数据库的密码
String url="jdbc:mysql://localhost:3306/mydb";
String user="root";
String pass="root";
// DriverManager驱动管理类获得了一个数据库连接对象
// Connection 接口负责java程序与数据库之间的连接
Connection cn=DriverManager.getConnection(url,user,pass);
System.out.println(cn);
}
}
用其他的配置文件保存数据库配置信息(更推荐)
配置文件生命在工程的src目录下【jdbc.properties】
说明:使用配置文件的方式保存数据库配置信息,在代码中加载配置文件。
使用配置文件的好处:
1.实现了代码和数据分离,如果需要修改数据库配置信息,直接在配置文中修改,不需要深入代码。
2.如果修改了配置信息,省去重新编译的过程。
配置文件生命在工程的src目录下【jdbc.properties】1
2
3url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8
user=root
pass=root
1 | package com.iweb.test; |
statement
操作和访问数据库
数据库连接用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回的结果,其实就一个数据库连接就是一个socket连接。
在java.sql包中有3各接口,分别定了对数据库的调用的不同方式
- Statement:用于执行静态SQL语句并返回它所生成的结果的对象。
- PrepatedStatement:SQL语句预编译对象,可以使用对象多次高效的执行该SQL语句。
statement
createStatement()用于创建statement对象,我们通过statement对象来完成对数据库的增删改查操
作。其实就是通过statement对象来将SQL语句发送给数据库,然后执行SQL语句。
该方法的具体的使用方法为:1
Statement st = cn.createStatement();
此处注意一下啊:
statement只能执行静态SQL语句,会有安全隐患,这个会在PrepatedStatement:SQL接口的时候讲解。(statement不推荐使用)
执行增删改查
执行对数据库的操作主要是执行statement中的方法。
执行给定的SQL语句,语句为insert、update、和delete语句,返回值是语句响应的行数。1
int executeUpdate(String sql)
实际实例:(DML操作)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48package com.iweb.test;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;
public class Test2 {2).查询数据
执行给定的查询的SQL语句,该语句返回ResultSet对象。
ResultSet(结果集对象)封装了SQL查询语句的结果
1.通过列名
public static void main(String[] args) throws Exception{
// 加载jdbc.properties文件
InputStream resourceAsStream
=Test2.class.getClassLoader().getResourceAsStream("jdbc.properties");
// Properties 继承于 HashTable,主要用于读取配置文件
Properties pros=new Properties();
// 加载文本信息到属性集
pros.load(resourceAsStream);
// 读取配置信息,依据KEY获取Value
String url = pros.getProperty("url");
String user = pros.getProperty("user");
String pass = pros.getProperty("pass");
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.驱动管理类获得一个连接对象
Connection cn = DriverManager.getConnection(url, user, pass);
// 3.通过Connection对象获得数据库操作工具(statement)
Statement st = cn.createStatement();
// 4.数据库操作工具(statement)来执行SQL语句
// st.executeQuery()用于执行查询操作 DQL
// st.executeUpdate() 用于执行增删改操作 DML
// 新增
// String sql="insert into emp
values(null,'1012','wangjie','男',55,'4313','2021-2-2',10,3000)";
// 修改
// String sql="update emp set name='王杰',age=58 where id=30";
// 删除
String sql="delete from emp where id=30";
int result = st.executeUpdate(sql);
if(result>0){
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
st.close();
cn.close();
}
}
查询数据操作
执行给定的查询的SQL语句,该语句返回ResultSet对象。1
ResultSet executeQuery(sql)
ResultSet(结果集对象)封装了SQL查询语句的结果
通过列名查找1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45package com.iweb.test;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class Test3 {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
InputStream is =
Test3.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(is);
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String pass = prop.getProperty("pass");
Connection cn = DriverManager.getConnection(url, user, pass);
// Statement st 操作数据库执行的工具
Statement st = cn.createStatement();
// 执行查询
String sql="select * from emp";
// 获得SQL查询语句的结果
ResultSet rs = st.executeQuery(sql);
// next判断是否还有下行数据,如果还有下一条数据,则返回true,否则false
// 循环结果集
while(rs.next()){
// 通过字段名
int id = rs.getInt("id");
String worknumber = rs.getString("worknumber");
String name = rs.getString("name");
String sex = rs.getString("sex");
int age = rs.getInt("age");
String card = rs.getString("card");
Date hiredate = rs.getDate("hiredate");
int dno = rs.getInt("dno");
int sal = rs.getInt("sal");
System.out.println("id:"+id+",worknumber:"+worknumber+",name:"+name+",sex:"+sex
+",age:"+age+",card:"+card+",hiredate:"+hiredate+",dno:"+dno+",sal:"+sal);
}
// 释放资源
rs.close();
st.close();
cn.close();
}
}
使用下标获取表中列的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34package com.iweb.test;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class Test4 {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
InputStream is =
Test4.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(is);
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String pass = prop.getProperty("pass");
Connection cn = DriverManager.getConnection(url, user, pass);
// Statement st 操作数据库执行的工具
Statement st = cn.createStatement();
// 准备一条SQL语句
String sql="select * from emp where id=20";
// 执行SQL语句
ResultSet rs = st.executeQuery(sql);
if(rs.next()){
// 通过下标
int id = rs.getInt(1);
String number = rs.getString(2);
String name = rs.getString(3);
System.out.println("id:"+id+"\t"+"number:"+number+"\t"+"name:"+name);
}
rs.close();
st.close();
cn.close();
}
}
关于Result接口的注意事项
1)注意类型不要获取错了
2)使用完毕要关闭结果集ResultSet,再关闭Statement,再关闭Connection(关闭原则)
综合案例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61package com.iweb.test;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
public class UserLogin {
public static void main(String[] args) {
// 用户从控制台输入账号和密码去登录
Scanner sc =new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine();
// 调用登录方法
login(username,password);
}
/**
* 登录功能
* @param username 登录账号
* @param password 登录密码
*/
public static void login(String username,String password){
Connection cn =null;
Statement st =null;
ResultSet rs=null;
try {
//1. 注册并加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.读取属性文件
InputStream is =
UserLogin.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(is);
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String pass = prop.getProperty("pass");
cn = DriverManager.getConnection(url, user, pass);
//3.连接Connection获得操作工具
st = cn.createStatement();
String sql="SELECT * FROM userinfo WHERE username='"+username+"' AND
PASSWORD='"+password+"'";
System.out.println(sql);
rs = st.executeQuery(sql);
if (rs.next()) {
System.out.println("欢迎"+username+"登录本系统!!");
}else{
System.out.println("账号或密码错误,请重新登录!!");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
rs.close();
st.close();
cn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
SQL注入问题(有面试题的喔)
当我们输出密码,发现账号和密码不正确竟然登录成功。
例如:执行以下结果机器现象
请输入用户名
admin
请输入密码
a’ or ‘1’=’1
SELECT * FROM userinfo WHERE username=’admin’ AND PASSWORD=’a’ or ‘1’=’1’
欢迎admin登录本系统!!
select * from userinfo where true 查询所有记录
以上案例出现的问题是:
我们让用户输入的密码和SQL语句进行字符串拼接,用户输入的内容作为SQL语句语法的一部分,改变
了原有SQL真正的意义,以上问题称为SQL注入,要解决SQL注入就不能让用户输入的密码和我们SQL
语句进行简单的字符串拼接。
使用Statement操作数据库表存在的弊端:
- 问题一:存在拼串操作,繁琐
- 问题二:存在SQL注入问题
对于java而言,要防范SQL注入,只要用 PreparedStatement(从Statement扩展而来)取代
Statement就可以了。
PreparedStatement
PreparedStatement也叫做预处理对象,它是Statement的子接口,两者功能相似,但是如果多次执行SQL语句时PreparedStatement效率会更高,它还可以给SQL语句传递参数,避免SQL注入问题。
预编译:将SQL语句发送给数据库预编译,PreparedStatement会引用这预编译后的结果,可以多次传
入不同的参数给PreparedStatement对象并执行,减少SQL编译次数,提高效率。
使用PreparedStatement的步骤
1). 编写SQL语句时,未知参数用?占位1
String sql="SELECT * FROM userinfo WHERE username=? AND PASSWORD=?";
2).通过链接对象获得PreparedStatement对象
3).设置实际参数的值 setXXX(占位符的位置,赋予真实的值)
4).执行带参数的SQL语句
5).关闭资源
在使用数据库语言时请在SQL中先行写好之后移植过来
案例(登录操作)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60public class UserLogin {
public static void main(String[] args) {
// 用户从控制台输入账号和密码去登录
Scanner sc =new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine();
// 调用登录方法
login(username,password);
}
/**
* 登录功能
* @param username 登录账号
* @param password 登录密码
*/
public static void login(String username,String password){
Connection cn =null;
ResultSet rs=null;
PreparedStatement ps=null;
try {
//1. 注册并加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.读取属性文件
InputStream is =
UserLogin.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(is);
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String pass = prop.getProperty("pass");
cn = DriverManager.getConnection(url, user, pass);
// 编写SQL语句时,未知参数用?占位
String sql="SELECT * FROM userinfo WHERE username=? AND PASSWORD=?";
// 通过连接获得预处理对象PreparedStatement
ps = cn.prepareStatement(sql);
// 设置实际参数的值 setXXX(占位符的位置,赋予真实的值)
ps.setString(1,username);
ps.setString(2,password);
// 执行带参数的SQL语句
rs = ps.executeQuery();
if(rs.next()){
System.out.println("登录成功:"+username);
}else{
System.out.println("账号或密码错误");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
rs.close();
ps.close();
cn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
实际案例(模糊查询)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46public class EmpLikeTest {
public static void main(String[] args) {
Scanner sc =new Scanner(System.in);
System.out.println("请输入要查询的职员姓名");
String name = sc.next();
Connection cn =null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
Class.forName("com.mysql.jdbc.Driver");
InputStream is = Test4.class.getClassLoader().
getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(is);
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String pass = prop.getProperty("pass");
cn = DriverManager.getConnection(url, user, pass);
// 未知参数?占位
String sql="select * from emp where name like ?";
// 获得预编译对象
ps = cn.prepareStatement(sql);
// 设置参数
ps.setString(1,"%"+name+"%");
// 返回结果集
rs=ps.executeQuery();
// 循环结果集
while(rs.next()){
System.out.println(rs.getString("name")+"\t"+
rs.getInt("age")+"\t"+
rs.getDate("hiredate"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
rs.close();
ps.close();
cn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
注意
使用jdbc进行模糊查询的时候”% _”不能写在SQL里面,写在执行的SQL有时候语法错误,所有我们要写setXXX()里面。
表与类的关系整张表看作是一个类,表的一行数据是类的一个实例对象。
1).查询一条数据,封装成一个Emp对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public class TestById {
public static void main(String[] args) {
Connection cn=null;
PreparedStatement ps=null;
ResultSet rs=null;
Emp emp=null;
try {
Class.forName("com.mysql.jdbc.Driver");
InputStream is =
Test3.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(is);
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String pass = prop.getProperty("pass");
cn = DriverManager.getConnection(url, user, pass);
String sql="select * from emp where id=?";
// 预编译对象
ps=cn.prepareStatement(sql);
// 赋值操作
ps.setInt(1,20);
// 执行
rs = ps.executeQuery();
if(rs.next()){
// 将查询的一行数据封装为Emp对象
emp=new Emp(rs.getInt("id"),
rs.getString("worknumber"),
rs.getString("name"),
rs.getString("sex"),
rs.getInt("age"),
rs.getString("card"),
rs.getDate("hiredate"),
rs.getInt("sal"),
rs.getInt("dno")
);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
rs.close();
ps.close();
cn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
System.out.println(emp.toString());
}
}
2).将多条数据封装称集合 List1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public class TestSelectAll {
public static void main(String[] args) {
Connection cn=null;
PreparedStatement ps=null;
ResultSet rs=null;
List<Emp> list =new ArrayList<Emp>();
try {
Class.forName("com.mysql.jdbc.Driver");
InputStream is =
Test3.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(is);
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String pass = prop.getProperty("pass");
cn = DriverManager.getConnection(url, user, pass);
// 预处理SQL语句
ps=cn.prepareStatement("select * from emp");
// 没有?要赋值
// 执行返回结果集
rs = ps.executeQuery();
while(rs.next()){
// 每一行数据封装成Emp对象
Emp emp=new Emp(rs.getInt("id"),
rs.getString("worknumber"),
rs.getString("name"),
rs.getString("sex"),
rs.getInt("age"),
rs.getString("card"),
rs.getDate("hiredate"),
rs.getInt("sal"),
rs.getInt("dno")
);
// 将封装的Emp对象添加到集合中
list.add(emp);
}
}catch (Exception e){
e.printStackTrace();
} finally {
try {
rs.close();
ps.close();
cn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
for(Emp e : list){
System.out.println(e.toString());
}
}
}
增删改操作
使用prepareStatement进行新增操作
1 | package com.iweb.test; |
使用prepareStatement进行修改操作
1 | public class TestEmpUpdate { |
使用prepareStatement进行删除操作
1 | public class TestEmpDelete { |
数据库工具类
需求:上面写的代码中出现了很多重复代码,可以把这些共同的代码抽取出来。
开始打包操作……呜噜噜
创建类JdbcUtils(其实这个很重要的,到时候我会发蓝图,所以只是简单总结一下)
JdbcUtils类的作用是封装数据库连接,以及释放数据库资源。
- 驱动程序、用户名、密码、URL定义为静态常量。
- 注册驱动使用static静态代码块实现,只需要加载一次。
- 提供获取数据库连接对象的静态方法。
- 释放系统资料的方法
包含三个方法:
1)用户名、密码、URL、驱动类定义为静态常量
2)获得数据库连接 getConnecation()
3)关闭所有打开的资源1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82package com.iweb.utils;
import com.iweb.test.Test4;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
// 1)用户名、密码、URL、驱动类定义为静态常量
private static String url;
private static String user;
private static String password;
private static String driver;
// 文件的读取,只需要读取一次即可拿到这些值,静态代码块
static{
try {
InputStream is = JdbcUtils.class.getClassLoader().
getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(is);
url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("pass");
driver=prop.getProperty("driverClass");
// 加载驱动
Class.forName(driver);
}catch (Exception e){
e.printStackTrace();
}
}
// 2)获得数据库连接 getConnecation()
public static Connection getConnection() throws Exception{
return DriverManager.getConnection(url,user,password);
}
// 3)关闭所有打开的资源 关闭顺序 ResultSet-->Statement-->Connection
public static void close(ResultSet rs, Statement st, Connection cn){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(st!=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(cn!=null){
try {
cn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Statement st, Connection cn){
if(st!=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(cn!=null){
try {
cn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试:
1 | public class TestJdbcUtils { |
数据库事务
事物是一组操作的集合,事务会把所有的操作作为一个整体一起向系统提交或撤销请求,这些操作要么同时成功,要么同时失败。
JDBC事务处理
1.数据一旦提交,就不可回滚。
2.数据什么时候意味着提交?
- 当一个连接对象被创建时,默认情况下是自动提交事务,每次执行一个SQL语句时,如果执行成功就会向数据自动提交,而不能回滚。
- 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的都是自己单独的连接,则无法保证。事务,即同一个事务的多个操作必须在同一个连接下。
JDBC程序中为了让多个SQL语句作为一个事务执行:
- 调用Connecation对象的setAutoCommit(false):以取消自动提交事务。
- 在所有的SQL语句都成功执行后,调用commit()提交事务。
- 在出现异常时,调用rollback()方法回滚事务
【案例:张三给李四转账】
1 | package com.iweb.test; |
这个部分灵活度很高,建议自己写代码,多调试几遍。话是什么说,但是还是多练吧
DAO结构
DAO:Data Access Object,数据访问对象。
项目文件标准文件结构(tree)1
2
3
4
5
6├───dao
│ ├───impl
│ └───interface
├───domain
├───service
└───utils
以上的各个文件夹的作用是:
- dao:数据访问层,负责与数据库进行交互,执行SQL语句。
- domain:领域模型层,负责定义业务模型。
- service:业务逻辑层,负责处理业务逻辑。
- utils:工具类层,负责提供一些常用的工具方法。
- impl:实现类层,负责实现DAO接口中的方法。
- interface:接口层,定义DAO接口。
- test:测试类层,负责编写测试代码。
反射
反射是指在运行时动态获取类的信息并调用其方法的能力。
反射机制
java反射机制在程序的运行时动态加载类并获取类的详情信息,从而操作类或对象的属性和方法。其本质是JVM得到Class对象之后,从而获取对象的各种信息。
反射机制主要包括以下几个方面:
- 类加载机制:将类的字节码文件加载到内存中,形成类的Class对象。
- 反射API:提供了一组用于操作类、对象和方法的API。
- 动态代理:在运行时创建代理对象,用于拦截和修改方法的调用。
类加载
类加载是指将类的字节码文件加载到内存中,形成类的Class对象的过程。
Java文件通过java编译器(javac)将java源代码(.java)编译而生成的。编译器将java代码转换成字节码,存储在class文件中,class文件需要加载到虚拟机中之后才能运行和使用。而虚拟机如何加载class文件,class文件中的信息进入到虚拟机后发生什么变化,这个过程涉及到了java的类加载机制。
类加载过程
主要由以下几个步骤组成:
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期可以概括为 7 个阶段:
加载、验证、准备、解析、初始化、使用和卸载。
1).加载(Loading)
加载是类加载过程的第一个阶段,在这个过程中,JVM会查找并加载类的二进制数据,通常通过类加载器(ClassLoader)完成。
类加载器:负责查找类文件,并将其加载到JVM中,JVM中内置了三个重要的ClassLoader:
- 启动类加载器(Bootstrap ClassLoader)
- 扩展类加载器(Extension ClassLoader)
- 系统类加载器(System ClassLoader)
2).链接(Linking)
链接阶段分为三个子阶段:验证、准备和解析
画个流程图
加载—>验证—>准备—>解析—>初始化—>使用—>卸载
- 验证(Verification):确保加载的类符合JVM规范,不会危害JVM的安全。
- 准备(Preparation):为类的变量分配内存并设置默认初始化(int为0,引用对象为null)。
- 解析(Resolution):将类、接口、字段和方法的符号引用转换为直接引用。
3).初始化(Initialization)
在初始化阶段,JVM会执行类中的静态初始化器和静态变量的赋值操作,初始化类变量和静态代码块。
4).使用(Using)
一旦类被初始化了,就可以被java程序使用了。可以通过new关键字创建类的实例。
5).卸载(Unloading)
当Java虚拟机结束时,或者在某个类不再被任务对象引用时,该类的类加载器可以卸载这个类。
1.2.2 双亲委派模型的核心机制
如果当一个类加载器收到类加载请求时,它不会先去加载,而是把这个请求委托父类加载器处理,如果父类加载器还存在其父类加载器,进一步向上委托,依次递归,最终将达到顶层的启动类加载器,如果父类加载器可以完成加载任务,就成功返回,倘如父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
class
class类是java反射机制的基础,反射机制主要是通过class类来实现的。
在java编程语言中,Class类是一个特殊的类,它用于表示JVM运行的类或者接口的信息。Class对象是在加载类时由Java虚拟机自动构建的,也就是不需要我们创建,JVM已经帮我们创建好了。
每个类在java中都对应一个Class对象,这个对象保存了该类的结构信息,比如类名、属性、方法等等。class类是一个反射工具,能提供很多的方法用于获取类的各种信息(构造方法,属性,方法,注解)。
获取类对象
java.lang.Class类的实例表示正在运行的Java应用程序中的类和接口。
获取类对象的方式(三种)
- Class.forName(“类的全路径名”)
- 类名.class
- 对象.getClass()
1
2
3
4
5
6
7
8
9
10// 1. Class.forName("类的全路径名")
Class<?> c1 = Class.forName("com.bettfil.java快速学习速查.domain.Account");
System.out.println(c1);
// 2. 类名.class
Class<Account> c2 = Account.class;
System.out.println(c2);
// 3. 对象.getClass()
Account account = new Account();
Class<? extends Account> c3 = account.getClass();
System.out.println(c3);
Class类的常用方法
- Field:获得对象的成员变量属性
- Method:获得对象的方法
- Constructor:获得对象的构造方法
- getConstructors:获得对象的构造方法
- Annotation:获得对象的注解
- getAnnotations:获得对象的注解
获取属性值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Test2 {
public static void main(String[] args) {
// 获取类对象
Class<Student> stuClass= Student.class;
// 获取属性
// getFields() 获得全部成员,包括继承而来的成员,不包括私有
// getDeclaredFields 获得本来定义的成员,包括私有,但是不包括继承而来
Field[] fields = stuClass.getDeclaredFields();
for(Field field : fields){
// getModifiers() 修饰符都是通过数字编号获取,要想获取具体内容
// filed.getType() 返回属性类型
// field.getName() 获得属性名
System.out.println(field.getModifiers()+"\t"+field.getType()+"\t"+field.getName());
}
}
}
如何看出来这个部分是反射呢?从哪个地方看出来的,有什么好处吗?
- 从类对象中获取属性,这个类对象是我们自己定义的,我们可以通过类对象获取到类中的属性。
- 反射机制的灵活性,我们可以在运行时动态地获取类的信息,包括属性、方法、构造函数等。
获取方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Test3 {
public static void main(String[] args) {
try {
Class stuClass = Class.forName("com.iweb.bean.Student");
// getMethods() 获取类中定义的方法和继承而来的方法
// getDeclaredMethods() 获取本类定义的方法,包括私有,但是不包括继承而来
Method[] methods = stuClass.getDeclaredMethods();
for(Method method : methods){
System.out.println(method);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
获取构造函数
1 | public class Test4 { |
获得注解
1 | public class Test5 { |
1.5 反射应用
Constructor构造方法
1 | package com.iweb.test; |
- 指定参数的构造
1 | public class Test6 { |
Field
反射获取成员并使用
1 | public static void method3(){ |
Method
调用本来的方法,一定要保证实例化对象,可以直接利用class类反射实例对象,反射调用方法。
1 | public static void method4(){ |
调用有返回值有参数的方法
1 | public static void method5(){ |
==总结==
在程序运行时,动态的加载类的信息(方法,属性,构造函数,注解),从而操作类或对象的属性和方法的这种机制称为Java反射。
实际案例
要求如下:
1.创建一个Emp类,定义私有的编码,私有的姓名和公开的年龄,3个属性,并创建set/get和toString方法,
添加无参构造,1个参数构造,2个参数构造赋值。
添加无参数无返回的study()方法
1 | public class Emp { |
2.测试以上三种获得类对象的方法
(1)通过类拿到字节码对象
new Emp().getClass()
(2)通过类的class属性拿到字节码对象
Emp.Class
(3) 通过类的路径拿到字节码对象
Class.forName()
3.获得类属性的方法。
(1)获得所有共有属性
(2)获得本类之中的属性
(3)为私有属性赋值,以及获取值。
4.利用反射创建对象
1 | import java.lang.reflect.Constructor; |
5.利用反射执行study方法
1 | import java.lang.reflect.Method; |
注解
Java注解(Annotation)是JDK引入的一种注释机制,注解可以作用在类,属性,方法上等。注解可以做到不改变改变逻辑的前提下在代码中嵌入补充信息。
注解本质就是一个接口。
元注解
用来标注注解的注解叫做元注解(JDK的内置注解):
@Target:设置注解作用的位置:
- type
- field
- method
@Retention:设置注解作用的时机:
- RetentionPolicy.RUNTIME:运行时(大多数情况使用它)
- RetentionPolicy.CLASS:注解在字节码文件中存在
- RetentionPolicy.SOURCE:注解仅存在源码中
Junit
测试分类
用Java写完程序之后需要测试准确性,然后每一次测试如果出现错误,就需要重新修改重新写,这是一个比较麻烦的过程。
黑盒测试:不需要写代码,输入值,看程序是否能够输出期望的值。
白盒测试:需要写代码,关注程序的具体执行流程。Junit就是一个百盒测试的工具。
Junit概述
Junit是java编程语言的单元测试工具,是一个非常重要的测试工具(白盒测试)
特点
- Junit是一个开放源代码的测试工具
- 提供注解来识别测试方法
- Junit测试可以让你编写代码更快,并提供之质量
- Junit在一个进度条中显示进度,如果运行良好则是绿色,如果运行失败,则是红色
使用步骤
步骤
1.将Junit的jar包导入到工程中
2.编写测试方法必须是共同的无参数无返回的测试方法。
3.在测试方面上使用@Test注解来标识该方法是一个测试方法。
4.选中测试方法右键通过Junit运行该方法
1 | public class Test7 { |
相关注解[应用]
注解 | 含义 |
---|---|
@Test | 测试方法 |
@Before | 在测试方法之前运行 |
@After | 在测试方法之后运行 |
代码示例
1 | package com.iweb.test; |
JDK8新特性
当前的java市场对JDK8和JDK9是主流,至于更高的11,17等,在企业中使用的最多的是JDK8和JDK9。